package person.wangchen11.editor.edittext;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.util.Log;

public class EditableWithLayout implements Editable,MyLayout {
	protected static final String TAG="EditableWithLayout";
	private int mLength=0;
	private char []mText=new char[0];
	private TextPaint mTextPaint = new TextPaint();
	private TextWatcher mTextWatcher=null;
    private int mSelectionStart=0;
    private int mSelectionEnd=0;
    private Rect mRect=new Rect();
    private Rect mRectLine=new Rect();
    private float mLineHeight;
    List<LineBody> mLineBodies=new ArrayList<LineBody>();
    List<SpanBody> mSpanBodies=new LinkedList<SpanBody>();
	public EditableWithLayout() {
		mTextPaint.setColor(Color.BLACK);
		mTextPaint.setTypeface(Typeface.MONOSPACE);
		mTextPaint.setTextSize(48);
		mLineHeight=50;
	}
	
	@Override
	public int length() {
		return mLength;
	}
	
	private void resizeTo(int size){
		if(mText.length>=size)
			return ;
		char[] text=new char[size+4096];
		System.arraycopy(mText, 0, text, 0, length());
		mText=text;
	}
	
	@Override
	public char charAt(int index) {
		return mText[index];
	}

	@Override
	public CharSequence subSequence(int start, int end) {
		return String.copyValueOf(mText, start, end-start);
	}

	@Override
	public void getChars(int start, int end, char[] dest, int destoff) {
        for (int i = start; i < end; i++)
            dest[destoff++] = mText[i];
	}
	
	@Override
	public void setSpan(Object what, int start, int end, int flags) {
		//Log.i(TAG, "setSpan");
		if( what instanceof TextWatcher )
		{
			mTextWatcher=(TextWatcher) what;
		}
		else
		if( what == Selection.SELECTION_START )
		{
			Log.i(TAG, "set SELECTION_START");
			mSelectionStart=start;
		}
		else
		if( what == Selection.SELECTION_END )
		{
			Log.i(TAG, "set SELECTION_END");
			mSelectionEnd=start;
		}
		else
		if( what instanceof ForegroundColorSpan ){
		}
	}

	public void sendTextBeforeChanged(CharSequence s,int start,int count,int after){
		if(mTextWatcher!=null)
			mTextWatcher.beforeTextChanged(s, start, count, after);
	}
	
	public void sendOnTextChanged(CharSequence s,int start,int before,int count){
		if(mTextWatcher!=null)
			mTextWatcher.onTextChanged(s, start, before, count);
	}
	
	public void sendTextAfterChanged(){
		if(mTextWatcher!=null)
		{
			mTextWatcher.afterTextChanged(this);
		}
	}
	
	@Override
	public void removeSpan(Object what) {
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T[] getSpans(int start, int end, Class<T> type) {
		
		return (T[]) Array.newInstance(type, 0);
	}

	@Override
	public int getSpanStart(Object tag) {
		if(tag == Selection.SELECTION_START)
			return mSelectionStart;
		if(tag == Selection.SELECTION_END)
			return mSelectionEnd;
		return 0;
	}

	@Override
	public int getSpanEnd(Object tag) {
		if(tag == Selection.SELECTION_END)
			return mSelectionEnd;
		if(tag == Selection.SELECTION_START)
			return mSelectionStart;
		return 0;
	}

	@Override
	public int getSpanFlags(Object tag) {
		return 0;
	}

	@SuppressWarnings("rawtypes")
	@Override
	public int nextSpanTransition(int start, int limit, Class type) {
		return 0;
	}

	@Override
	public Editable replace(int st, int en, CharSequence text, int start,int end) {
		//Log.i(TAG, "replace:st:"+st+" en:"+en+" text:"+text+" start:"+start+" end:"+end);
		int deleteLen=en-st;
		int insertLen=end-start;
		int addLen=insertLen-deleteLen;
		resizeTo(length()+addLen);
		if(deleteLen<0||insertLen<0)
			return this;
		if(deleteLen==0&&insertLen==0)
			return this;
		if(mSelectionStart==st && mSelectionEnd==en){
			mSelectionStart=mSelectionEnd=en+addLen;
		}
		else
		{
			if(mSelectionStart>=st){
				mSelectionStart+=addLen;
				if(mSelectionStart<st)
					mSelectionStart=st;
			}
			if(mSelectionEnd>=st){
				mSelectionEnd+=addLen;
				if(mSelectionEnd<st)
					mSelectionEnd=st;
			}
		}
		sendOnTextChanged(this, st, length(), addLen);
		sendTextBeforeChanged(this, st,addLen ,length()+addLen);

		if(addLen>0)
			charsCopyEndToStart(mText, en, mText, en+addLen, length()+addLen);
		else
		if(addLen<0)
			charsCopyStartToEnd(mText, en, mText, en+addLen, length()+addLen);
		if(insertLen>0)
			TextUtils.getChars(text, start, end, mText, st);
		mLength+=addLen;
		if(mSelectionStart<0)
			mSelectionStart=0;
		if(mSelectionEnd<0)
			mSelectionEnd=0;
		if(mSelectionStart>mLength)
			mSelectionStart=mLength;
		if(mSelectionEnd>mLength)
			mSelectionEnd=mLength;
			
		analysisLines();
		sendTextAfterChanged();
		return this;
	}

	@Override
	public Editable replace(int st, int en, CharSequence text) {
		return replace(st, en, text, 0, text.length());
	}

	@Override
	public Editable insert(int where, CharSequence text, int start, int end) {
		int len = end-start;
		if(where<0 || where>length() || len<=0)
			return this;
		return replace(where,where, text, start, end);
	}

	static void charsCopyStartToEnd(char []src,int where,char []dst,int start,int end){
		for(;start<end;start++,where++){
			dst[start]=src[where];
		}
	}
	static void charsCopyEndToStart(char []src,int where,char []dst,int start,int end){
		int srcend=where+end-start;
		for(;end>=start;end--,srcend--){
			dst[end]=src[srcend];
		}
	}
	
	@Override
	public Editable insert(int where, CharSequence text) {
		return insert(where,text,0,text.length());
	}

	@Override
	public Editable delete(int st, int en) {
		return replace(st, en, null, 0, 0);
	}

	@Override
	public Editable append(CharSequence text) {
		return null;
	}

	@Override
	public Editable append(CharSequence text, int start, int end) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Editable append(char text) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void clear() {
		sendOnTextChanged(this, 0, length(), length());
		sendTextBeforeChanged(this, 0, length(), 0);
		mLength=0;
		sendTextAfterChanged();
	}

	@Override
	public void clearSpans() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void setFilters(InputFilter[] filters) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public InputFilter[] getFilters() {
		return null;
	}

	@Override
	public int getLineForOffset(int start) {
		if(mLineBodies.isEmpty())
			return 0;
		for(int i=0;i<mLineBodies.size();i++)
		{
			LineBody body=mLineBodies.get(i);
			if(body.in(start))
				return i;
		}
		return mLineBodies.size()-1;
	}

	@Override
	public float getPrimaryHorizontal(int start) {
		int line=getLineForOffset(start);
		LineBody body= mLineBodies.get(line);
		
		float horizontal=0f;
		try {
			horizontal=getPaint().measureText(this, body.mStart, start);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return horizontal;
	}

	@Override
	public void getLineBounds(int line, Rect rect) {
		rect.left=0;
		rect.right=Integer.MAX_VALUE;
		float top=line*mLineHeight;
		rect.top=(int) top;
		rect.bottom=(int) (top+mLineHeight);
	}

	@Override
	public int getLineForVertical(int top) {
		int line=(int) (top/mLineHeight);
		if(line<0)
			line=0;
		if(line>=getLineCount())
			line=getLineCount()-1;
		return line;
	}

	@Override
	public void draw(Canvas canvas) {
		canvas.getClipBounds(mRect);
		int startLine=getLineForVertical(mRect.top);
		int endLine=getLineForVertical(mRect.bottom);
		Paint paint=getPaint();
		float descent=mTextPaint.getFontMetrics().descent;
		for(int i=startLine;i<=endLine;i++)
		{
			getLineBounds(i, mRectLine);
			LineBody body=mLineBodies.get(i);
			canvas.drawText(mText, body.mStart, body.length(), mRectLine.left, mRectLine.bottom-descent, paint);
		}
		//canvas.drawText(mText, 0, length(), 0, 400, getPaint());
	}
	
	private void analysisLines(){
		mLineBodies.clear();
		int start=0;
		for(int i=0;i<mLength;i++){
			if(mText[i]=='\n'){
				mLineBodies.add(new LineBody(start, i+1));
				start=i+1;
			}
		}
		mLineBodies.add(new LineBody(start, mLength));
	}
	
	@Override
	public int getLineCount() {
		return mLineBodies.size();
	}

	@Override
	public Paint getPaint() {
		return mTextPaint;
	}
	
	@Override
	public int getOffsetForHorizontal(int line, float x) {
		LineBody body=mLineBodies.get(line);
		Paint paint = getPaint();
		float sumx=0;
		int pos=body.mStart;
		if(x<=0)
			return pos;
		for(;pos<body.mEnd;pos++){
			float tx=paint.measureText(mText, pos, 1);
			sumx+=tx;
			if(x<=sumx)
				return pos;
		}
		int lineCount=getLineCount();
		//最后一行没有包含\n  
		if(line==lineCount-1)
			return body.mEnd;
		return body.mEnd-1;
	}

	@Override
	public int getHeight() {
		return (int) (getLineCount()*mLineHeight);
	}	

	@Override
	public String toString() {
		return String.copyValueOf(mText, 0, length());
	}
}

class SpanBody{
	Object mSpan;
	int mStart;
	int mEnd;
	int mFlag;
	public SpanBody(Object span,int start,int end,int flag) {
		mSpan=span;
		mStart=start;
		mEnd=end;
		mFlag=flag;
	}
}

class LineBody{
	int mStart;
	int mEnd;
	public LineBody(int start,int end) {
		mStart=start;
		mEnd=end;
	}
	public boolean in(int pos){
		if(pos>=mStart && pos<mEnd)
			return true;
		return false;
	}
	public int length()
	{
		return mEnd-mStart;
	}
}